home *** CD-ROM | disk | FTP | other *** search
- ; PARKERN.ASM
- ;
- ; Lowlevel routines for PARnet
- ; 03-JUN-93 First PC version <S.A.Pechler@bdk.tue.nl>
- ; 03-JUL-93 Fixed bug when transferring odd number of bytes.
- ; Optimized by putting some counters in registers <S.A.Pechler>
- ; 07-JUL-93 Bugfix: ES was not set when reading into overflow buffer.
- ; Deleted leading underscores from all data labels.
- ; Added ParReadV and ParWriteV functions. <S.A.Pechler>
- ; 11-AUG-93 Bugfix: When an error occured in parread(), the errornumber was
- ; set in the wrong register (was BX, had to be AX). <S.A.Pechler>
- ; 01-SEP-93 Optimized the functions by using block move operations (STOSB).
- ; Changed the register usage of read_data() and write_data().
- ; 02-SEP-93 Rewrote all private routines for usage without the special
- ; PARnet interface (connections between 2 machines only).
- ;
- ;
- ; PARALLEL PORT NETWORK LOW LEVEL ROUTINES
- ;
- ; THE CABLE:
- ; (2-9) D7-D0 ---------- D7-D0 (2-9)
- ; PC ( 1) STROBE ---------- BUSY (11) AMIGA
- ; PARALLEL <= (14) AUTOLF ---------- POUT (12) => PARALLEL
- ; CARD (16) INIT --+----+-- SEL (13) PORT
- ; (10) ACK -/ \- ACK (10)
- ; (18-22) GND ---------- GND (18-22)
- ;
- ; Cable connections between PC and Amiga WITHOUT(!) an interface
- ;
- ;
- ;
- ; (2-9) D7-D0 ---------- D7-D0 (2-9)
- ; PC ( 1) STROBE ---------- STROBE ( 1) PC
- ; PARALLEL <= (14) AUTOLF ---------- AUTOLF (14) => PARALLEL
- ; CARD (16) INIT --+----+-- INIT (16) CARD
- ; (10) ACK -/ \- ACK (10)
- ; (18-22) GND ---------- GND (18-22)
- ;
- ; Cable connections between 2 PC's WITHOUT(!) an interface
- ;
- ;
- ; Parallel port usage:
- ;
- ; DATA PORT : used for data input & output
- ; CONTROL PORT: used for control input & output
- ;
- ; All lines pulled up. Thus, asserted state is a 0. Idle
- ; state is an undriven (1). Protocol transfers a byte at
- ; a time. Protocol is ethernet style with a small window
- ; of error in the line aquisition routine.
- ;
- ; Note: Timeouts should be set around a second. Ideally
- ; defaults should be fixed on faster machines.
- ;
- ; LINE description:
- ;
- ; PARnet: Cable: PC: Description:
- ; ---------------------------------------------------------
- ; ~ACK BUSY STROBE hand shake
- ; ~REQ POUT AUTOLF hand shake
- ; CTL SEL INIT 1 = special byte, 0 = data byte.
- ; (for address mark, valid when ~REQ
- ; goes low. For EOP mark, sample
- ; when ~REQ goes high)
- ;
- ; See the associated routines for the exact low-level protocols.
- ;
- ;
- ; PROTOCOL DESCRIPTION:
- ;
- ; HandShake (Reader <- Writer transfer). A Handshake
- ; sequence transfers TWO bytes of information.
- ;
- ; WRITER READER
- ; |-> place data, ~REQ->0
- ; | wait for ~REQ->0
- ; | read data & store
- ; | set ~ACK->0
- ; | wait ~ACK->0
- ; | place data, ~REQ->1
- ; | wait for ~REQ->1
- ; | read data & store
- ; | set ~ACK->1
- ; | wait ~ACK->1
- ; |<- LOOP (2 bytes written) LOOP (2 bytes read)
- ;
- ;
- ; Read: (1) Determine if your machine is being addressed
- ; (~REQ=0, data=myaddress, CTL=1)
- ; (1) set ~ACK to output and
- ; (2) Handshake sequence for the address mark, only
- ; first byte valid.
- ; (3) Handshake sequence for data util rcv byte
- ; with CTL = 1 (EOP), byte must == 0.
- ; (4) Set ~ACK to input
- ;
- ; Write: (1) AQUIRE THE NETWORK (see below)
- ; involves gaining control and then setting the
- ; CTL and ~REQ to outputs.
- ;
- ; Also checks if somebody is writing to us,
- ; in which case -2 is returned instantaniously
- ; indicating we should do a ParRead().
- ;
- ; (2) Handshake sequence for address mark, send dest
- ; address (second byte garbage). Note that CTL->1
- ; *BEFORE* we set ~REQ->0
- ; (3) Handshake sequence for data bytes
- ; (4) Handshake sequence for EOP mark (Note that CTL->1
- ; *AFTER* we get the ~ACK->1 and before release
- ; ~REQ (->1). Only firstbyte valid and set to 0.
- ;
- ; (5) Set ~ACK and ~CTL to inputs
- ;
- ; AQUIRE: Line aquisition prevents two people from writing to
- ; the net at the same time.
- ;
- ; * A line is considered aquired if ANY of the 3
- ; control lines is 0.
- ; * If network is not aquired:
- ; - set ~ACK to output and 1
- ; - bclr ~ACK to 0 and bne success
- ; (else set ~ACK to input and try again)
- ; - set data lines to output and place my address
- ; (Must ]be done before CTL is glitched)
- ; - set ~CTL to output and 1
- ; - set CTL to 0 and then 1 (glitch it) to cause
- ; FLAG interrupt on all other machines
- ; - set ~REQ to output and 0 (beginning of handshake
- ; sequence)
- ; - lastly, release ~ACK by setting it to an input
- ;
- ; Note that at all times at least one line is a 0
- ; so no other machine will attempt to aquire the net.
- ;
- ; Note that the destination address is placed on the data
- ; lines after we have aquired the line but before we glitch
- ; the CTL line to cause an interrupt. This allows the
- ; other machines to instantaniously determine who is being
- ; addressed.
- ;
- ; Note that the CTL line gets glitched at the end of a packet
- ; too for the EOP mark. In this case there is a 0 on the
- ; data lines so while an interrupt is generated, nobody
- ; thinks they are being addressed.
- ;
- ;
- ; ASSEMBLING/COMPILING the code:
- ;
- ; When assembling, use the following flags (turbo-assembler V2.0):
- ;
- ; tasm -mx -t -DMEMMOD=large parkern.asm
- ; option -zi adds full debug information.
- ;
- ; When including the routines in C, use the following prototypes:
- ;
- ; typedef unsigned short int16;
- ; extern void paraddress(int16,int16);
- ; extern int pardataready(void);
- ; extern int parreadV();
- ; extern int parread(unsigned char *,int16);
- ; extern int parwriteV();
- ; extern int parwrite(int16, unsigned char *,int16);
- ;
-
- .MODEL MEMMOD,C
- LOCALS
- %MACS
- .LALL
-
- .DATA
- ParNetAddr db 1 ; my PARnet address (default)
- LPTData dw 0378h ; LPT data port address (default)
- LPTStatus dw 0379h ; LPT status port address (default)
- LPTControl dw 037ah ; LPT control port address (default)
-
- ParLLTimeout dw 65535
- ;should be dl 983025 ; default timeout value (about 1 second?)
- DestAddress db 5 ; Destination address for write
- Dummybuf db 0 ; dummy buffer
- db 0 ; dummy buffer
- db 0,0 ; padding
- NextRVector db 0 ; Flag: vector to next read buffer present
- NextWVector db 0 ; Flag: vector to next write buffer present
-
- .CODE
- PUBLIC paraddress,pardataready,parreadV,parread,parwrite,parwriteV
-
- ;============================================================================
- ;Private Routines
- ;
- ;Don't use these in any program.
-
-
- ;----------------------------------------------------------------------------
- ; Stable
- ;
- ; Put the interface in a stable mode by setting the data and control lines
- ; to high.
- ;
- stable:
- push ax
- push dx
- mov dx,LPTData ;data register
- mov al,-1 ;datalines high
- out dx,al ;set it
- mov dx,LPTControl ;Control register
- mov al,14h ;all outputs to 1 (INIT is not inverted!), enable IRQ7
- out dx,al ;write control
- pop dx
- pop ax
- ret
-
- ;----------------------------------------------------------------------------
- ;Data Input
- ;
- ; Set the datalines to 'input' by setting all lines to high
- ;
- data_input:
- push ax
- push dx
- mov dx,LPTData ;data register
- mov al,-1 ;datalines high
- out dx,al ;set it
- pop dx
- pop ax
- ret
-
- ;----------------------------------------------------------------------------
- ; Control Input
- ;
- ; Set the control-lines to 'input' by setting all lines to high
- ;
- ;control_input:
- ; push ax
- ; push dx
- ; mov dx,LPTControl ;Control register
- ; mov al,14h ;all outputs to 1 (INIT is not inverted!), enable IRQ7
- ; out dx,al ;set it
- ; pop dx
- ; pop ax
- ; ret
-
- ;----------------------------------------------------------------------------
- ; Read Data
- ;
- ; read data from the cable, put it in AL
- ;
- read_data: ;data line must be set to INPUT first!
- push dx
- mov dx,LPTData ;data register
- in al,dx ;read it
- pop dx
- ret
-
- ;----------------------------------------------------------------------------
- ; Read Control
- ;
- ; Read the controlbits from the cable (busy, pout & sel)
- ;
- ; BL: control read from control register, represents the REAL line
- ; status (so if a line is +5V, then the bit is 1).
- ;
- ; BL bits: 7654 3210 centronics: cable: parnet:
- ; value: XXXX X100
- ; |||| |||`- strobe busy ack
- ; |||| ||`-- auto LF pout req
- ; |||| |`--- init sel ctl
- ; |||| `---- selin - -
- ; ````------ invalid - -
- ;
- read_control: ;control lines must be set to input first!
- push ax
- push dx
- mov dx,LPTControl ;Control register
- in al,dx ;read it
- xor al,0bh ;invert all but init
- and al,07 ;discard bits not needed
- mov bl,al ;return value in BL
- pop dx
- pop ax
- ret ;ready, controlbits in BL
-
- ;-----------------------------------------------------------------------------
- ;Clear ACK Only
- ;
- ; Clear the parnet ack-bit. Leave other control lines high.
- ; Warning: I can't read my own control-lines back!
- ;
- clear_ack_only:
- push bx
- mov bl,0feh ; set parnet ack-bit to 0
- call write_control ; write control
- pop bx
- ret
-
- ;----------------------------------------------------------------------------
- ;Set ACK All
- ;
- ; Set the parnet ack-bit. Leave other control lines high.
- ; Warning: I can't read my own control-lines back!
- ;
- Set_ack_all:
- push bx
- mov bl,0fh ; set all bits to 1 (including ACK)
- call write_control ; write control
- pop bx
- ret
-
- ;----------------------------------------------------------------------------
- ; Write control
- ;
- ; BL: controlbits to be written
- ;
- ; BL bits: 7654 3210 centronics
- ; value: XXXX X100 keyword: cable: controlbits:
- ; |||| |||`- strobe busy ack
- ; |||| ||`-- auto LF pout req
- ; |||| |`--- init sel ctl
- ; |||| `---- selin - -
- ; ````------ invalid - -
- ;
- ; The value in BL represents the REAL LINE STATUS, so a bit=0 means the
- ; line is on low voltage.
- ;
- ; The INIT output is on the parallel card not inverted. This bit will be
- ; inverted in this procedure, so you don't have to care about it.
- ;
- write_control:
- push ax
- push dx
- mov dx,LPTControl ; control register
- mov al,bl ; move control bits to AL (for OUT-instruction)
- xor al,0bh ; invert all lines except for INIT (=ctl)
- out dx,al ; write control
- pop dx
- pop ax
- ret
-
- ;----------------------------------------------------------------------------
- ; Write DATA
- ;
- ; Put Data on the line.
- ;
- ; AL: data to be written.
- ;
- write_data: ; interface must be in stable mode!
- push dx
- mov dx,LPTData ; data register
- out dx,al ; put data on line
- pop dx
- ret
-
- ;============================================================================
- ;Public routines
-
-
- ; (void) paraddress(int16 myaddr,int16 LPTAddress)
- ;
- ; Set my ParNet address (1-254) and LPT port address (0378h,03bch or 0278h)
- ;
- ; ParNet addresses 0 and 255 are reserved!
- ;
- paraddress PROC
- ARG myad:word,lptad:word
-
- mov ax,myad ; my parnet address
- mov ParNetAddr,al ; store address
- mov ax,lptad ; LPT port address
- mov LPTData,ax ; Place data port address
- inc ax ; next register is status port
- mov LPTStatus,ax
- inc ax ; next register is control port
- mov LPTControl,ax
- call stable ; all lines high
- ret
-
- paraddress ENDP
-
- ; int = pardataready(void)
- ;
- ; Check for data present (e.g. after an IRQ7).
- ;
- ; Returns: 1 if packet is probably pending for you
- ; 0 if line is currently idle
- ; -1 if packet isn't for you
- ;
- ; If line has been aquired but no control address has been
- ; put on it yet, pardataready() will wait for a control
- ; address. Thus, after a signal, a single call to
- ; pardataready() should suffice.
- ;
- pardataready PROC
- call stable ; be sure all lines are set to input
- .pdstable:
- call read_control ; read control in BL
- mov cl,bl ; save it
- call read_data ; read data in AL
- call read_control ; read control in BL
- cmp cl,bl
- jne .pdstable
-
- ; Now, pardataready might be called after the sending machine
- ; has aquired but before it can assert REQ. However, the
- ; sending machine has already (guarenteed) placed its address
- ; on the data port. So while the address matches, loop while
- ; REQ not asserted.
-
- test bl,02 ; ~REQ asserted?
- jz .pd10 ; yes
- cmp [ParNetAddr],al ; no, does data match anyway?
- je .pdstable ; YES, loop until get ~REQ or
- jmp short .pdfail ; data bad.
-
- .pd10:
- test bl,04 ; ~REQ is asserted, CTL=1 ?
- jz .pdrn ; no, middle of some packet
- cmp [ParNetAddr],al ; yes, my address?
- jne .pdrn ; nope
- mov ax,1 ; yes, packet (probably) for us
- jmp short .pdend
-
- .pdfail:
- test bl,04 ; fail due to ~REQ not asserted
- jz .pdrn ; CTL=0, line busy
- xor ax,ax ; line idle.
- jmp short .pdend
-
- .pdrn:
- mov ax,-1 ; line busy, packet not for me
- .pdend:
- ret
-
- pardataready ENDP
-
- ;n = parreadV(unsigned char *buf,int16 bytes, buf2,bytes2, ..., NULL, NULL)
- ;n = parread(unsigned char *buf,int16 bytes)
- ;
- ;Read a pending packet.
- ;
- ;Returns: n = -1 (1 second timeout, no packet pending)
- ; n = 0 to bytes-1 (1 second timeout after transmission interrupted)
- ; n = bytes (success), or n > bytes (transmitting machine's packet
- ; was larger than we can handle, extra bytes thrown out)
- ;
- ;NOTE: Requesting an odd number of bytes is O.K. but if you request N where
- ; N is odd and the writer sends N + 1 you will never know (N will be
- ; returned). See also parwrite() below.
- ;
-
- parreadV PROC
- ARG buf:ptr, byts:word, nextbuf: ptr, nextbyts:word
-
- mov al,1
- mov NextRVector,al ; secondary buffer(s) present
- jmp short parRbegin
-
- parreadV ENDP
-
- parread PROC
- ARG buf:ptr, byts:word, nbuf:ptr, nextbyts:word
- ; ^these fake params are needed for reference.
- mov al,0 ; no secondary buffer(s)
- mov NextRVector,al
-
- parRbegin:
- if @Datasize NE 0
- push ds ; the 'USES' macro doesn't work here
- push es
- push si
- push di
- les di,buf ; es:di = buf, my data segment needs to
- ; stay intact.
- else
- push ds
- push es
- push si
- push di
- mov ax,ds
- mov es,ax ; to simulate a mov es,ds
- mov di,buf ; es:di = buf (ds already set)
- endif
-
- cld ; all moves will be forward
- mov si,byts ; maximum number of bytes to read
- call stable ; ensure line not asserted
- mov DX,[ParLLTimeout] ; DX = timeout load
-
- ; Wait loop for address mark
- ; Ctl = 1, ~DReq = 0
- .rmstab:
- call read_control ; read control in BL
- mov cl,bl ; save it
- call read_data ; read data in AL
- call read_control ; read control in BL
- cmp cl,bl ; control lines stable?
- jne .rmstab ; nope
- test bl,04 ; expect CTL=1
- jz .rms1 ; nope
- test bl,02 ; expect ~REQ=0
- jz .rms2 ; yes
-
- .rms1:
- dec dx ; decrement timeout count
- jnz .rmstab ; no timeout.
- mov ax,-1 ; return value: read timeout
- jmp .rmend ; no address mark!
-
- .rms2:
- cmp [ParNetAddr],al ; My address?
- jne .rms1 ; no, timeout loop
-
- ; Got my address, ~Ack byte.
-
- mov DX,[ParLLTimeout] ; reset timeout
- call Clear_Ack_Only ; set ~Ack to 0
-
- .rms4: call read_control ; get control
- test bl,02 ; wait for ~REQ to go away
- jnz .rms5
- dec dx ; decrement timeout count
- jnz .rms4
- mov ax,-2 ;~REQ not released?
- jmp .rmend
-
- .rms5: call Set_Ack_All ; release ~ACK
-
- mov cx,0 ; set # of bytes read to 0
- jmp .rms10 ; skip past move
-
-
- ; MAIN READ LOOP
- ;
- ; cx holds count (number of bytes read).
- ; DI buffer pointer (place to store data).
-
- .rms10loop:
- ; mov es:[di],al ; store data
- ; inc di ; next address
- stosb
-
- .rms10:
- call Read_Control
- test bl,02 ; wait for ~REQ asserted
- jz .rms20
- call Read_control
- test bl,02 ; again
- jz .rms20
- mov DX,[ParLLTimeout] ; reset timeout
-
- .rms11: call Read_Control
- test bl,02 ; wait for ~REQ asserted with timeout
- jz .rms20
- dec dx ; decrement timeout count
- jnz .rms11
- mov ax,-1
- jmp short .rmend ; timeout
-
- .rms20: call read_data ; get data in al and
- call Clear_Ack_Only ; assert ~ACK
-
- ; note on CTL = 1 end sequence this data item is a dummy
-
- ; mov es:[di],al ; store data
- ; inc di ; next address
- stosb
-
- inc cx ; optimized, but not quite true,
- inc cx ; we've only written one 1 sf
-
- call Read_Control
- test bl,02 ; wait for ~REQ released
- jnz .rms30
- call Read_Control
- test bl,02 ; again
- jnz .rms30
- mov DX,[ParLLTimeout] ; reset timeout
- .rms21:
- call Read_Control
- test bl,02 ; wait for ~REQ released with timeout
- jnz .rms30
- dec dx ; decrement timeout count
- jnz .rms21 ; no timeout yet?
- jmp short .rmendsub ; sub because CX is 2 ahead
-
-
- .rms30:
- call read_data ; get data in al
- call Read_Control ; get CTL status in BL
- call Set_Ack_All ; release ~ACK
- test bl,04 ; EOP if CTL=1
- jnz .rmeop
-
- ; CANNOT STORE DATA HERE! In case odd # bytes requested,
- ; second byte would overflow buffer (each handshake sequence ALWAYS
- ; transfers 2 bytes of information).
-
- dec si ; # of bytes remaining
- jz .rmodd ; already zero, # of bytes were odd
- dec si
-
- jz .rmste ; reached zero (even bytes)
- jmp .rms10loop ; continue if >0.
-
- .rmodd: dec si ; si must be -1 when odd # bytes requested
- dec cx ; fixup count (was one ahead)
- jmp short .rmeven
-
- .rmste:
- mov es:[di],al ; if si = 0, its's even and we
- ; should store the last byte.
- .rmeven:
- cmp NextRVector,0 ; if next buffer does not exist
- je .rmovflow ; overflow
- .rmsev0:
- cmp WORD PTR nbuf,0 ; next buffer points to somewhere?
- if @Datasize NE 0
- jne .rmsev1
- cmp WORD PTR nbuf+2,0 ; check also the segment
- else
- je .rmovflow
- endif
- .rmsev1:
- mov si,nextbyts ; length of next buffer
- if @Datasize NE 0
- les di,nbuf ; load pointer to next buffer
- add bp,6 ; this is VERY tricky = sizeof(nbuf+nbyts)
- else
- mov di,nbuf ; load pointer to next buffer
- add bp,4 ; this one is also VERY tricky
- endif
- or si,si ; size of this buffer = 0 ?
- jz .rmsev0 ; yes, then check for next buffer
- jmp .rms10 ; proceed reading
-
- .rmovflow:
- if @Datasize NE 0
- mov di,ds
- mov es,di ; to simulate move es,ds
- endif
-
- mov di,OFFSET Dummybuf ; overflow, use dummy buffer
-
- jmp .rms10
-
- .rmeop:
- cmp al,0 ; EOP data better be 0!
- je .rmendsub
- mov ax,-3 ; bad protocol
- jmp short .rmend
-
- .rmendsub:
- dec cx ; because we were two ahead
- dec cx
- mov ax,cx ; return value in AX
- .rmend:
- call data_input
- call Set_Ack_All ; setting ~ACK to input
- call stable
- pop di
- pop si
- pop es ; normally the 'USES' macro inserts these
- pop ds
- ret ; return value in AX
-
- parread ENDP
-
- ;n = parwriteV(int 16 destaddr, unsigned char *buf, int16 bytes,
- ; buf2, bytes2, ..., NULL, NULL)
- ;n = parwrite(int16 destaddr, unsigned char *buf, int16 bytes)
- ;
- ;Write a packet.
- ;
- ;Returns: n = -2 Cannot write anything, a packet is pending
- ; (instantanious)
- ; n = -1 Destination machine does not respond (1 sec timeout)
- ; n = N N bytes written ok (success if n == bytes)
- ;
- ; NOTE: sending an odd number of bytes is O.K. but if you write N where
- ; N is odd and the reader requests N + 1 he will get N + 1 the last
- ; byte being garbage.
- ;
- parwriteV PROC
- ARG dest:word, buf:ptr, byts:word, nextbuf:ptr, nextbyts:word
-
- mov al,1
- mov NextWVector,al
- jmp short parWbegin
-
- parwriteV ENDP
-
- parwrite PROC
- ARG dest:word, buf:ptr, byts:word, nbuf:ptr, nextbyts:word
- ; ^these fake params are needed for
- ; reference.
- mov al,0
- mov NextWVector,al ; no secondary buffer(s)
-
- parWbegin:
- if @Datasize NE 0
- push ds ; the 'USES' macro doesn't work here
- push es
- push si
- push di
- les si,buf ; es:si = source-buffer to be written
- else
- push ds
- push es
- push si
- push di
- mov ax,ds
- mov es,ax ; to simulate a mov es,ds
- mov si,buf ; es:si = buf (ds already set)
- endif
-
- cld ; all moves will be forward
- mov ax,dest ; destination address
- mov DestAddress,al ; can't do a direct move to DestAddress
- mov di,byts ; number of bytes to write
-
-
- call stable ; ensure line not asserted
- mov DX,[ParLLTimeout] ; DX = timeout load
-
- .wmstab:
- cli ; disable interrupts
- call Read_Control
- mov cl,bl
- call read_data
- call Read_Control ; get stable control
- cmp cl,bl
- je .wmstab1
- sti ; set interrupts
- jmp short .wmstab
-
- ; Interrupts still disabled
- ; BL holds ~ACK ~REQ and CTL status
-
- .wmstab1:
- mov ax,-2 ; number of bytes written yet (=none)
- cmp bl,07h ; ~ACK=1, ~REQ=1, CTL=1 ?
- je .wm02
-
- ; if CTL=1, ~REQ=0 and AL=my address then
- ; return with -2
-
- test bl,02h ; ~REQ = 0?
- jne .wm01 ; nope
- test bl,04h ; CTL = 1?
- je .wm01 ; nope
- cmp [ParNetAddr],al ; somebody is calling me?
- jne .wm01 ; nope
-
- sti ; enable interrupts
- jmp .wmend
-
- .wm01:
- sti ; enable interrupts
- dec dx ; decrement timeout count
- jnz .wmstab
- jmp .wmend
-
- ; interrupts still disabled
- ; we almost own the line
-
- .wm02:
- call Read_Control
- call Clear_Ack_Only ; assert ~ACK
- test bl,01
- jne .wm05 ; ACK was released before, have line!
-
- call Set_Ack_All ; release ~ACK
- jmp short .wm01
-
- ; Line now aquired.
-
- .wm05:
- sti ; enable interrupts
- mov al,DestAddress
- call Write_Data ; set destination address on datalines
-
- ; Before asserting ~REQ, pulse CTL to cause interrupt on remote
- ; machines. Note that our address is already on the datalines.
-
- mov bl,02h ; leave ~ACK=0 and ~REQ=1, but CTL->0
- call Write_Control
- mov bl,06h ; leave ~ACK=0 and ~REQ=1, but CTL->1
- call Write_Control
- mov bl,04h ; leave ~ACK=0 and CTL=1, but ~REQ->0
- call Write_Control ; assert REQ
- mov bl,05h ; leave CTL=1 and ~REQ=0, but ~ACK->1
- call Write_Control ; release ACK
- ; (note that REQ->0 before ACK->released)
-
- mov ax,-1 ; number of bytes written yet (none)
-
- ; interrupts enabled for transfer (fully handshaked)
- ;
- ; Address mark ~ACK, wait for ~ACK asserted.
- .wm10:
- call Read_Control
- test bl,01 ; ~ACK asserted?
- jz .wm15 ; yes, remote machine got my address mark
- mov DX,[ParLLTimeout] ; DX = timeout count
- .wm11:
- call Read_Control
- test bl,01
- jz .wm15
- dec dx ; decrement timeout count
- jnz .wm11 ; timeout?
- jmp .wmend ; yes.
-
- ; got ack, now set CTL = 0 (leaves at least one line 0 so
- ; nobody else thinks the bus is idle!)
- ;
- ; note: Since this is the address mark, and is sampled by
- ; the reader before it asserts ~ACK, I can set CTL
- ; = 0 now instead of waiting till after ~ACK is
- ; released.
-
- .wm15:
- mov bl,01h ; leave ACK=1, REQ=0, but CTL->0
- call Write_Control ; set CTL = 0 for duration of packet
- mov bl,03h ; leave ACK=1, CTL=0, but REQ->1
- call Write_Control ; release ~REQ
-
- mov cx,0 ; number of bytes written (none)
-
- ; Data transfer loop
- ;
- ; wait for ~ACK to be released (-> 1). If no more bytes to
- ; write, then skip to .wm50
-
- .wm20:
- or di,di ; more data in this buffer?
- jz .wm50 ; nope
-
- call Read_Control
- test bl,01 ; wait for ~ACK to be released
- jnz .wm30
- mov DX,[ParLLTimeout] ; DX = timeout countdown
- .wm21:
- call Read_Control
- test bl,01
- jnz .wm30 ; need the timeout here?
- call Read_control
- test bl,01 ; check ACK again
- jnz .wm30
- dec dx
- jnz .wm21
- mov ax,cx ; return value in AX
- jmp .wmend ; timeout
-
- ; Assert ~REQ for this data byte and wait for ~ACK
-
- .wm30:
- mov al,es:[si] ; get next data byte.
- inc si ; this can't be done with a LODSB :-(
-
- call Write_Data ; store data and ..
- mov bl,01h ; leave ACK=1, CTL=0, but REQ->0
- call Write_Control ; .. assert ~REQ
-
- mov al,es:[si] ; get next data bytes
- inc si ; this can't be done with a LODSB :-(
-
- inc cx ; number of bytes written (this only)
- ; (not valid until we get ACK which
- ; is why the wmendsub is included)
-
- call Read_Control
- test bl,01 ; wait for ACK asserted
- jz .wm40
- call Read_Control
- test bl,01 ; look again
- jz .wm40
- mov DX,[ParLLTimeout] ; DX = timeout count
- .wm31:
- call Read_Control
- test bl,01 ; wait for ACK asserted with timeout
- jz .wm40
- dec dx ; decrement timeout count
- jnz .wm31
- jmp short .wmendsub ; timeout, no bytes written!
-
- ; have ~ACK, so byte transmitted. increment bytes written,
- ; decr. of bytes left was already done before.
- ; now send second byte and loop back.
-
- .wm40: call Write_Data ; data was loaded in AL before.
- mov bl,03h ; leave ACK=1, CTL=0, but REQ->1
- call Write_Control ; release ~REQ
- inc cx ; increment number of bytes written
-
- dec di ; one less bytes
- jz .wm40a ; odd number of bytes were written
- dec di ; one less bytes
- jz .wm50 ; these were the last even bytes(s)
-
- jmp .wm20 ; loop back
-
- .wm40a: dec di ; fixup, di = -1 when odd bytes requested
- jmp short .wm50 ; last odd byte, go send an EOP
-
- ; Last byte in buffer has been transmitted.
- ;
- ; Get next buffer in vector.
-
- .wm50: cmp NextWVector,0 ; if next buffer does not exist
- je .wm50a ; yes.
- cmp WORD PTR nbuf,0 ; next buffer points to somewhere?
- if @Datasize NE 0
- jne .wmnext
- cmp WORD PTR nbuf+2,0 ; check also the segment
- endif
- je .wm50a ; is zero, so no next buffer.
- .wmnext:
- mov di,nextbyts ; length of next buffer
- if @Datasize NE 0
- les si,nbuf ; load pointer to next buffer
- add bp,6 ; this is VERY tricky = sizeof(nbuf+nbyts)
- else
- mov si,nbuf ; load pointer to next buffer
- add bp,4 ; this one is also VERY tricky
- endif
- jmp .wm20 ; proceed write.
-
- .wm50a:
- ; Last byte has been transmitted,
- ;
- ; Wait for ~ACK to be released and then assert ~REQ with
- ; EOP & CTL = 1
- ;
- ; (timing on read is that CTL is sampled when ~REQ is
- ; RELEASED so no timing window here)
-
- call Read_Control ; Wait ~ACK released
- test bl,01
- jz .wm50
-
- mov al,0
- call Write_Data ; EOP mark (=0)
- mov bl,01h ; leave ACK=1 and CTL=0, but REQ->0
- call Write_Control ; assert ~REQ
-
- ; Wait for ~ACK asserted
-
- call Read_Control
- test bl,01 ; ACK asserted?
- jz .wm60 ; yes
- mov DX,[ParLLTimeout] ; DX = timeout count
- .wm51:
- call Read_Control
- test bl,01 ; wait for ACK asserted with timeout
- jz .wm60
- dec dx ; decrement timeout count
- jnz .wm51
- mov ax,-3 ; EOP failed
- jmp short .wmend
-
- ; Set CTL=1 then release ~REQ, then wait for ~ACK released
- .wm60:
- mov bl,05h ; leave ACK=1, REQ=0, but CTL->1
- call Write_Control ; release CTL
- mov bl,07h ; leave ACK=1, CTL=1, but REQ->1
- call Write_Control ; release ~REQ
-
- ; Wait ~ACK released ?
-
- .wm61: call Read_Control
- test bl,01
- jz .wm61
-
- ; Add DI to CX. This handles fixup if an odd number of bytes were
- ; requested written, DI will be -1 (odd) or 0 (even) and CX will
- ; be one too large (odd) or perfect (even)
-
- add cx,di
- jmp short .wmend1
-
- .wmendsub:
- dec cx ; was ahead in count
- .wmend1:
- mov ax,cx ; return value in AX
- .wmend:
- call stable ; set all lines to input
- pop di
- pop si
- pop es
- pop ds ; normally done by the 'USES' macro
- ret ; return value in AX
-
- parwrite ENDP
-
- END
-